/*
 * Copyright (c) Kumo Inc. and affiliates.
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace melon::detail {
    template<class I>
    inline PolyVal<I>::PolyVal(PolyVal &&that) noexcept {
        that.vptr_->ops_(Op::eMove, &that, static_cast<Data *>(this));
        vptr_ = std::exchange(that.vptr_, vtable<I>());
    }

    template<class I>
    inline PolyVal<I>::PolyVal(PolyOrNonesuch const &that) {
        that.vptr_->ops_(
            Op::eCopy, const_cast<Data *>(that._data_()), PolyAccess::data(*this));
        vptr_ = that.vptr_;
    }

    template<class I>
    inline PolyVal<I>::~PolyVal() {
        vptr_->ops_(Op::eNuke, this, nullptr);
    }

    template<class I>
    inline Poly<I> &PolyVal<I>::operator=(PolyVal that) noexcept {
        vptr_->ops_(Op::eNuke, _data_(), nullptr);
        that.vptr_->ops_(Op::eMove, that._data_(), _data_());
        vptr_ = std::exchange(that.vptr_, vtable<I>());
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    template<class T, std::enable_if_t<ModelsInterface<T, I>::value, int> >
    inline PolyVal<I>::PolyVal(T &&t) {
        using U = std::decay_t<T>;
        static_assert(
            std::is_copy_constructible<U>::value || !Copyable::value,
            "This Poly<> requires copyability, and the source object is not "
            "copyable");
        // The static and dynamic types should match; otherwise, this will slice.
        assert(
            typeid(t) == typeid(std::decay_t<T>) &&
            "Dynamic and static exception types don't match. Object would "
            "be sliced when storing in Poly.");
        if (inSitu<U>()) {
            auto const buff = static_cast<void *>(&_data_()->buff_);
            ::new(buff) U(static_cast<T &&>(t));
        } else {
            _data_()->pobj_ = new U(static_cast<T &&>(t));
        }
        vptr_ = vtableFor<I, U>();
    }

    template<class I>
    template<class I2, std::enable_if_t<ValueCompatible<I, I2>::value, int> >
    inline PolyVal<I>::PolyVal(Poly<I2> that) {
        static_assert(
            !Copyable::value || std::is_copy_constructible<Poly<I2> >::value,
            "This Poly<> requires copyability, and the source object is not "
            "copyable");
        auto *that_vptr = PolyAccess::vtable(that);
        if (that_vptr->state_ != State::eEmpty) {
            that_vptr->ops_(Op::eMove, PolyAccess::data(that), _data_());
            vptr_ = &select<I>(*std::exchange(that_vptr, vtable<std::decay_t<I2> >()));
        }
    }

    template<class I>
    template<class T, std::enable_if_t<ModelsInterface<T, I>::value, int> >
    inline Poly<I> &PolyVal<I>::operator=(T &&t) {
        *this = PolyVal(static_cast<T &&>(t));
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    template<class I2, std::enable_if_t<ValueCompatible<I, I2>::value, int> >
    inline Poly<I> &PolyVal<I>::operator=(Poly<I2> that) {
        *this = PolyVal(std::move(that));
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    inline void PolyVal<I>::swap(Poly<I> &that) noexcept {
        switch (vptr_->state_) {
            case State::eEmpty:
                *this = std::move(that);
                break;
            case State::eOnHeap:
                if (State::eOnHeap == that.vptr_->state_) {
                    std::swap(_data_()->pobj_, that._data_()->pobj_);
                    std::swap(vptr_, that.vptr_);
                    return;
                }
                [[fallthrough]];
            case State::eInSitu:
                std::swap(
                    *this, static_cast<PolyVal<I> &>(that)); // NOTE: qualified, not ADL
        }
    }

    template<class I>
    inline AddCvrefOf<PolyRoot<I>, I> &PolyRef<I>::_polyRoot_() const noexcept {
        return const_cast<AddCvrefOf<PolyRoot<I>, I> &>(
            static_cast<PolyRoot<I> const &>(*this));
    }

    template<class I>
    constexpr RefType PolyRef<I>::refType() noexcept {
        using J = std::remove_reference_t<I>;
        return std::is_rvalue_reference<I>::value
                   ? RefType::eRvalue
                   : std::is_const<J>::value
                         ? RefType::eConstLvalue
                         : RefType::eLvalue;
    }

    template<class I>
    template<class That, class I2>
    inline PolyRef<I>::PolyRef(That &&that, Type<I2>) {
        auto *that_vptr = PolyAccess::vtable(PolyAccess::root(that));
        detail::State const that_state = that_vptr->state_;
        if (that_state == State::eEmpty) {
            throw BadPolyAccess();
        }
        auto *that_data = PolyAccess::data(PolyAccess::root(that));
        _data_()->pobj_ = that_state == State::eInSitu
                              ? const_cast<void *>(static_cast<void const *>(&that_data->buff_))
                              : that_data->pobj_;
        this->vptr_ = &select<std::decay_t<I> >(
            *static_cast<VTable<std::decay_t<I2> > const *>(that_vptr->ops_(
                Op::eRefr, nullptr, reinterpret_cast<void *>(refType()))));
    }

    template<class I>
    inline PolyRef<I>::PolyRef(PolyRef const &that) noexcept {
        _data_()->pobj_ = that._data_()->pobj_;
        this->vptr_ = that.vptr_;
    }

    template<class I>
    inline Poly<I> &PolyRef<I>::operator=(PolyRef const &that) noexcept {
        _data_()->pobj_ = that._data_()->pobj_;
        this->vptr_ = that.vptr_;
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    template<class T, std::enable_if_t<ModelsInterface<T, I>::value, int> >
    inline PolyRef<I>::PolyRef(T &&t) noexcept {
        _data_()->pobj_ =
                const_cast<void *>(static_cast<void const *>(std::addressof(t)));
        this->vptr_ = vtableFor<std::decay_t<I>, AddCvrefOf<std::decay_t<T>, I> >();
    }

    template<class I>
    template<
        class I2,
        std::enable_if_t<ReferenceCompatible<I, I2, I2 &&>::value, int> >
    inline PolyRef<I>::PolyRef(Poly<I2> &&that) noexcept(
        std::is_reference<I2>::value)
        : PolyRef{that, Type<I2>{}} {
        static_assert(
            Disjunction<std::is_reference<I2>, std::is_rvalue_reference<I> >::value,
            "Attempting to construct a Poly that is a reference to a temporary. "
            "This is probably a mistake.");
    }

    template<class I>
    template<class T, std::enable_if_t<ModelsInterface<T, I>::value, int> >
    inline Poly<I> &PolyRef<I>::operator=(T &&t) noexcept {
        *this = PolyRef(static_cast<T &&>(t));
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    template<
        class I2,
        std::enable_if_t<ReferenceCompatible<I, I2, I2 &&>::value, int> >
    inline Poly<I> &PolyRef<I>::operator=(Poly<I2> &&that) noexcept(
        std::is_reference<I2>::value) {
        *this = PolyRef(std::move(that));
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    template<
        class I2,
        std::enable_if_t<ReferenceCompatible<I, I2, I2 &>::value, int> >
    inline Poly<I> &PolyRef<I>::operator=(Poly<I2> &that) noexcept(
        std::is_reference<I2>::value) {
        *this = PolyRef(that);
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    template<
        class I2,
        std::enable_if_t<ReferenceCompatible<I, I2, I2 const &>::value, int> >
    inline Poly<I> &PolyRef<I>::operator=(Poly<I2> const &that) noexcept(
        std::is_reference<I2>::value) {
        *this = PolyRef(that);
        return static_cast<Poly<I> &>(*this);
    }

    template<class I>
    inline void PolyRef<I>::swap(Poly<I> &that) noexcept {
        std::swap(_data_()->pobj_, that._data_()->pobj_);
        std::swap(this->vptr_, that.vptr_);
    }

    template<class I>
    inline AddCvrefOf<PolyImpl<I>, I> &PolyRef<I>::get() const noexcept {
        return const_cast<AddCvrefOf<PolyImpl<I>, I> &>(
            static_cast<PolyImpl<I> const &>(*this));
    }
} // namespace melon::detail
